/**
 * Copyright (C) 2021-2022 maminjie <canpool@163.com>
 * SPDX-License-Identifier: LGPL-2.1
 * Powered by Qt-Advanced-Docking-System
**/

#include "docktabbar.h"

#include "dockpannel.h"
#include "docktab.h"

#include <QBoxLayout>
#include <QMouseEvent>
#include <QScrollBar>

QADS_BEGIN_NAMESPACE

class DockTabBarPrivate
{
public:
    DockTabBarPrivate(DockTabBar *parent);

    void updateTabs();

    inline bool validIndex(int index) const { return index >= 0 && index < q->count(); }

    DockTab *firstTab() const { return q->tab(0); }
    DockTab *lastTab() const { return q->tab(q->count() - 1); }

public:
    DockTabBar *q;
    DockPannel *m_dockPannel = nullptr;
    QWidget *m_tabsContainerWidget = nullptr;
    QBoxLayout *m_tabsLayout = nullptr;
    int m_currentIndex = -1;
}; // class DockTabBarPrivate

DockTabBarPrivate::DockTabBarPrivate(DockTabBar *parent)
    : q(parent)
{}

void DockTabBarPrivate::updateTabs()
{
    // Set active TAB and update all other tabs to be inactive
    for (int i = 0; i < q->count(); ++i) {
        auto tab = q->tab(i);

        if (!tab) {
            continue;
        }

        if (i == m_currentIndex) {
            tab->show();
            tab->setActive(true);
            q->ensureWidgetVisible(tab);
        } else {
            tab->setActive(false);
        }
    }
}

DockTabBar::DockTabBar(DockPannel *parent)
    : QScrollArea(parent)
    , d(new DockTabBarPrivate(this))
{
    d->m_dockPannel = parent;
    setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
    setAttribute(Qt::WA_StyledBackground, true);
    setFrameStyle(QFrame::NoFrame);
    setWidgetResizable(true);
    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

    d->m_tabsContainerWidget = new QWidget();
    d->m_tabsContainerWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
    d->m_tabsContainerWidget->setObjectName("tabsContainerWidget");
    d->m_tabsLayout = new QBoxLayout(QBoxLayout::LeftToRight);
    d->m_tabsLayout->setContentsMargins(0, 0, 0, 0);
    d->m_tabsLayout->setSpacing(0);
    d->m_tabsContainerWidget->setLayout(d->m_tabsLayout);
    setWidget(d->m_tabsContainerWidget);

    setFocusPolicy(Qt::NoFocus);
}

DockTabBar::~DockTabBar()
{
    delete d;
}

void DockTabBar::insertTab(int index, DockTab *tab)
{
    if (!d->validIndex(index)) {
        index = count();
        d->m_tabsLayout->addWidget(tab);
    } else {
        d->m_tabsLayout->insertWidget(index, tab);
    }

    connect(tab, &DockTab::clicked, this, &DockTabBar::onTabClicked);
    connect(tab, &DockTab::moved, this, &DockTabBar::onTabMoved);
    connect(tab, &DockTab::closeRequested, this, &DockTabBar::onTabCloseRequested);
    connect(tab, &DockTab::closeOtherRequested, this, &DockTabBar::onTabCloseOtherRequested);
    connect(tab, &DockTab::elidedChanged, this, &DockTabBar::tabElidedChanged);
    tab->installEventFilter(this);
    emit tabInserted(index);

    if (index <= d->m_currentIndex) {
        setCurrentIndex(d->m_currentIndex + 1);
    } else if (d->m_currentIndex == -1) {
        setCurrentIndex(index);
    }

    updateGeometry();
}

void DockTabBar::removeTab(DockTab *tab)
{
    if (!count()) {
        return;
    }

    int newCurrentIndex = currentIndex();
    int removeIndex = d->m_tabsLayout->indexOf(tab);

    if (count() == 1) {
        newCurrentIndex = -1;
    }

    if (newCurrentIndex > removeIndex) {
        newCurrentIndex--;
    } else if (newCurrentIndex == removeIndex) {
        newCurrentIndex = -1;

        // First we walk to the right to search for the next visible tab
        for (int i = (removeIndex + 1); i < count(); ++i) {
            if (this->tab(i)->isVisibleTo(this)) {
                newCurrentIndex = i - 1;
                break;
            }
        }

        // If there is no visible tab right to this tab then we walk to
        // the left to find a visible tab
        if (newCurrentIndex < 0) {
            for (int i = (removeIndex - 1); i >= 0; --i) {
                if (this->tab(i)->isVisibleTo(this)) {
                    newCurrentIndex = i;
                    break;
                }
            }
        }
    }

    emit tabRemoving(removeIndex);
    d->m_tabsLayout->removeWidget(tab);
    tab->disconnect(this);
    tab->removeEventFilter(this);

    if (newCurrentIndex != d->m_currentIndex) {
        setCurrentIndex(newCurrentIndex);
    } else {
        d->updateTabs();
    }

    updateGeometry();
}

void DockTabBar::closeTab(int index)
{
    if (!d->validIndex(index)) {
        return;
    }

    auto tab = this->tab(index);

    if (tab->isHidden()) {
        return;
    }

    emit tabCloseRequested(index);
}

int DockTabBar::count() const
{
    return d->m_tabsLayout->count();
}

int DockTabBar::currentIndex() const
{
    return d->m_currentIndex;
}

void DockTabBar::setCurrentIndex(int index)
{
    if (!d->validIndex(index) || index == d->m_currentIndex) {
        return;
    }

    emit currentChanging(index);
    d->m_currentIndex = index;
    d->updateTabs();
    updateGeometry();
    emit currentChanged(index);
}

DockTab *DockTabBar::currentTab() const
{
    return tab(d->m_currentIndex);
}

DockTab *DockTabBar::tab(int index) const
{
    if (!d->validIndex(index)) {
        return nullptr;
    }

    return qobject_cast<DockTab *>(d->m_tabsLayout->itemAt(index)->widget());
}

bool DockTabBar::isTabVisible(int index) const
{
    if (!d->validIndex(index)) {
        return false;
    }

    return tab(index)->isVisible();
}

QSize DockTabBar::minimumSizeHint() const
{
    QSize size = sizeHint();
    size.setWidth(10);
    return size;
}

QSize DockTabBar::sizeHint() const
{
    return d->m_tabsContainerWidget->sizeHint();
}

bool DockTabBar::eventFilter(QObject *watched, QEvent *event)
{
    bool result = QScrollArea::eventFilter(watched, event);
    DockTab *tab = qobject_cast<DockTab *>(watched);

    if (!tab) {
        return result;
    }

    switch (event->type()) {
    case QEvent::Hide:
        emit tabClosed(d->m_tabsLayout->indexOf(tab));
        updateGeometry();
        break;
    case QEvent::Show:
        emit tabOpened(d->m_tabsLayout->indexOf(tab));
        updateGeometry();
        break;
    // Setting the text of a tab will cause a LayoutRequest event
    case QEvent::LayoutRequest:
        updateGeometry();
        break;
    default:
        break;
    }

    return result;
}

void DockTabBar::wheelEvent(QWheelEvent *event)
{
    event->accept();
    const int direction = event->angleDelta().y();

    if (direction < 0) {
        horizontalScrollBar()->setValue(horizontalScrollBar()->value() + 20);
    } else {
        horizontalScrollBar()->setValue(horizontalScrollBar()->value() - 20);
    }
}

void DockTabBar::onTabClicked()
{
    DockTab *tab = qobject_cast<DockTab *>(sender());
    if (!tab) {
        return;
    }

    int index = d->m_tabsLayout->indexOf(tab);
    if (index < 0) {
        return;
    }

    setCurrentIndex(index);
    emit tabBarClicked(index);
}

void DockTabBar::onTabCloseRequested()
{
    DockTab *tab = qobject_cast<DockTab *>(sender());
    int index = d->m_tabsLayout->indexOf(tab);
    closeTab(index);
}

void DockTabBar::onTabCloseOtherRequested()
{
    auto senderTab = qobject_cast<DockTab *>(sender());

    for (int i = 0; i < count(); ++i) {
        auto currentTab = tab(i);
        if (currentTab->isClosable() && !currentTab->isHidden() && currentTab != senderTab) {
            // If the dock widget is deleted with the closeTab() call, its tab it will no longer
            // be in the layout, and thus the index needs to be updated to not skip any tabs
            int offset = currentTab->dockWidget()->features().testFlag(DockWidget::DockWidgetDeleteOnClose) ? 1 : 0;
            closeTab(i);
            // If the the dock widget blocks closing, i.e. if the flag
            // CustomCloseHandling is set, and the dock widget is still open,
            // then we do not need to correct the index
            if (currentTab->dockWidget()->isClosed()) {
                i -= offset;
            }
        }
    }
}

void DockTabBar::onTabMoved(const QPoint &globalPos)
{
    DockTab *movingTab = qobject_cast<DockTab *>(sender());
    if (!movingTab) {
        return;
    }

    int fromIndex = d->m_tabsLayout->indexOf(movingTab);
    auto mousePos = mapFromGlobal(globalPos);
    mousePos.rx() = qMax(d->firstTab()->geometry().left(), mousePos.x());
    mousePos.rx() = qMin(d->lastTab()->geometry().right(), mousePos.x());
    int toIndex = -1;

    // Find tab under mouse
    for (int i = 0; i < count(); ++i) {
        DockTab *dropTab = tab(i);
        if (dropTab == movingTab || !dropTab->isVisibleTo(this)
                || !dropTab->geometry().contains(mousePos)) {
            continue;
        }
        toIndex = d->m_tabsLayout->indexOf(dropTab);
        if (toIndex == fromIndex) {
            toIndex = -1;
        }
        break;
    }

    if (toIndex > -1) {
        d->m_tabsLayout->removeWidget(movingTab);
        d->m_tabsLayout->insertWidget(toIndex, movingTab);
        emit tabMoved(fromIndex, toIndex);
        setCurrentIndex(toIndex);
    } else {
        // Ensure that the moved tab is reset to its start position
        d->m_tabsLayout->update();
    }
}

QADS_END_NAMESPACE
